#include "common.h"

int main(int argc, char* argv[]) {

	int sock_serv; // listening socket
	int sockfd; // connection socket
	short int port; //port number
	struct sockaddr_in servaddr; // socket address struct
	char buffer[MAXBUF]; // character buffer
	char *endptr;  // for strtol()
	fd_set active_fd_set, tmpfds; // for select()
	int tr = 1, i=0, size=0, k=0,num=0, j=0, found = -1;
	int serv_count=0, client_count = 0; // servers and clients counters
	char* admin = NULL;

	/* Messages */
	char server_adv[] = "100\n"; // Server advertisement
	char msg2[] = "Entrez pseudo destinataire\n";
	char msg3[] = "Entrez message\n";
	char list[MAXBUF]="Liste des connectés\n";
	char msg[] = "Quel est votre pseudo ?\n";
	char list2[MAXBUF] = "Liste des connectés du groupe\n";
	char msg5[] = "-fs ";

	/* for strtok to extract string separated by ':' */
	const char delimiters[] = ":";
	char cp[MAXBUF];
	char *token3, *token4, *token5;

	/* Readline and Sendline buffers */
	char buffer1[MAXBUF];
	char buffer2[MAXBUF];
	char buffer3[MAXBUF];
	char buffer4[MAXBUF];
	char buffer5[MAXBUF];
	char buffer6[MAXBUF];

	/* Used to find clients by nickname */
	client_t x;
	client_t y;

	/* Servers and Clients lists */
	client_t client[MAX_CLIENTS];
	client_t client_glob[MAX_CLIENTS];
	group_t groups[MAX_GROUPS];

	FD_ZERO (&active_fd_set);

	/* Initialize list to null values  */
	for (num = 0; num<MAX_CLIENTS; num++){
		client[num].fd = 0;
		client[num].pseudo = NULL;
	}
	for (num = 0; num<MAX_CLIENTS; num++){
		client_glob[num].fd = -1;
		client_glob[num].pseudo = NULL;
	}


	/* Get port number from the command line , and set to default port if
     no arguments were supplied
	 */

	if ( argc >= 2 ) {
		port = strtol(argv[1], &endptr, 0);
		if ( *endptr ) {
			fprintf(stderr, "ECHOSERV: Invalid port number.\n");
			exit(EXIT_FAILURE);
		}
	}
	else if ( argc < 2 ) {
		port = PORT;
	}

	int choice = 0;
	char* ip2 = NULL;
	ip2 = malloc(sizeof(char)* 20);
	char* port2 = NULL;
	port2 = malloc(sizeof(char)* 20);
	int* server_sock[SERVERS];
	for (i=0; i < SERVERS; i++){
		server_sock[i] = NULL;
		server_sock[i] = malloc(sizeof(int));
	}

	printf("Serveur:\n");
	while ( choice != 2) {
		/* Display a menu to enable servers to connect to other servers  */
		printf("1. Connection à un serveur.\n");
		printf("2. Accepter les connections.\n");
		if (choice == 2) break;
		scanf("%d", &choice);

		switch (choice){
		case 1 :
			printf("Donner l'adresse IP du serveur\n");
			scanf("%s", ip2);
			printf("Donner le port du serveur\n");
			scanf("%s", port2);
			/* Connect another server */
			create_socket_connect(ip2, port2, server_sock[atoi(argv[2])-2+serv_count]);
			FD_SET (*server_sock[atoi(argv[2])-2+serv_count], &active_fd_set);
			printf("Sendline to: %d\n", *server_sock[atoi(argv[2])-2+serv_count]);
			/* Notify the other server that this connect() comes from a server  */
			Sendline(*server_sock[atoi(argv[2])-2+serv_count], server_adv, strlen(server_adv));
			/* Increment the servers counter */
			serv_count++;
			break;
		case 2 :
			printf("Accepting connections ...\n");
			break;
		default:
			printf("Le caractère que vous avez entré n'est pas valide\n");
			printf("Réessayez!\n");
			break;
		}
	}

	/* Create the listening socket */

	if ( (sock_serv = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
		fprintf(stderr, "ECHOSERV: Error creating listening socket.\n");
		exit(EXIT_FAILURE);
	}

	/*Set all bytes in socket address structure to zero,
    and fill in the relevant data members */

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(port);

	/* Bind our socket addresss to the listening socket,
     and call listen()*/

	if (setsockopt(sock_serv,SOL_SOCKET,SO_REUSEADDR,&tr,sizeof(int)) == -1) {
		perror("setsockopt");
		exit(1);
	}
	if ( bind(sock_serv, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0 ) {
		fprintf(stderr, "ECHOSERV: Error calling bind()\n");
		exit(EXIT_FAILURE);
	}

	if ( listen(sock_serv, LISTENQ) < 0 ) {
		fprintf(stderr, "ECHOSERV: Error calling listen()\n");
		exit(EXIT_FAILURE);
	}

	printf("Attente de connexion\n");

	/* FD_ZERO() clears out the different fdsets, so that
     it doesn't contain any file descriptors.*/

	FD_SET (sock_serv, &active_fd_set);

	/*We initially have only one socket open, to receive new
    incoming connections. Initialise accordingly the tmpfds 
    set with select MACRO*/


	/* Enter an infinite loop to respond to client requests and echo input*/

	int max = sock_serv;

	if (argc > 2) {
		for (i = 0; i < atoi(argv[2])-1 ; i++ ){
			if (max < *server_sock[i]) max =  *server_sock[atoi(argv[2])-2];
		}
	}

	while (1) {
		tmpfds = active_fd_set;
		if (select (max+1, &tmpfds, NULL, NULL, NULL) < 0)
		{
			perror ("select");
			exit (EXIT_FAILURE);
		}

		/* The select() system call waits until any of the file descriptors
       specified in the read, sets given to it, is ready to give data */

		for (i = 3; i <= max; ++i) {
			if (FD_ISSET (i, &tmpfds)) {
				if (i == sock_serv) {
					/* Connection request on original socket. */
					size = sizeof (servaddr);
					sockfd = accept (sock_serv, (struct sockaddr *) &servaddr,
							(socklen_t *) &size);
					if (sockfd < 0)
					{
						perror ("accept");
						exit (EXIT_FAILURE);
					}
					fprintf (stderr,"Server: connect from host %s, port %d.\n",
							inet_ntoa(servaddr.sin_addr),
							(int) ntohs (servaddr.sin_port));
					FD_SET (sockfd, &active_fd_set);

					if(sockfd > max) max = sockfd;
				}

				/*Make the difference between a new client connection and data
      	  receveid from an already existing client*/
				else
				{
					if (client[i].fd == 0){
						client[i].fd = i;
					}

					memset(buffer,'\0',sizeof(buffer));
					memset(buffer4,'\0',sizeof(buffer4));

					/* Data arriving on an already-connected socket. */
					/* Central buffer where both servers and clients send data */
					Readline(i, buffer, MAXBUF-1);
					printf("Central buffer %s", buffer);

					/* If the header of the buffer is "100":
					 *  a server connection notification */
					if (buffer[0] == '1' && buffer[1] == '0' && buffer[2] == '0' ){
						client[i].type = 1; // then type of this client is 1:server
						for (k=0; k< max+1; k++){
							if (client[k].type == 1){
								printf("A server is connected\n");
							}
						}
						for (k=0; k < serv_count; k++){
							printf("A server is connected\n");
						}
					}

					/* If the header of the buffer is "101": a server sent a message */
					if (buffer[0] == '1' && buffer[1] == '0' && buffer[2] == '1' ){
						strcpy(buffer2, &buffer[3]); // delete the header
						printf("Received from a server: %s", buffer2);

						/* If the sub-header of the buffer is "re":
						  a client is connected to another server */
						if (buffer2[0] == 'r' && buffer2[1] == 'e'){
							// Add the client to the global list
							printf("Adding: %s to the global list\n", &buffer2[2]);
							client_glob[client_count].pseudo = malloc(strlen(&buffer2[2]));
							strcpy(client_glob[client_count].pseudo, &buffer2[2]);
							client_count++; // increment the global number of connected clients
						}

						/* If the sub-header of the buffer is "f->":
							a client wants to send a file */
						char *token, *token2;
						if (buffer2[0] == 'f' && buffer2[1] == '-' && buffer2[2] == '>'){
							// Change "f->" sub-header to "-fs" that a client can respond to
							buffer2[0] = '-';
							buffer2[1] = 'f';
							buffer2[2] = 's';
							// Extract relevant data to forward to the client
							token = strtok (buffer2, delimiters);
							token2 = strtok (NULL, delimiters);
							printf("DEMANDE D'ENVOI DE FICHIER\n");
							y.fd = -1;
							// &token[4] contains the receiver's nickname, find his socket
							for (k=0; k< MAX_CLIENTS; k++){
								if (client[k].pseudo!=NULL){
									if (!strncmp(client[k].pseudo, &token[4],
											minChar(strlen(&token[4]),
													strlen(client[k].pseudo)))){
										y = client[k];
										break;
									}
								}
							}
							printf("DE: %sPOUR: %s\n", token2, &token[4]);
							strcpy(buffer6, token2);
							if (y.fd > 0) {
								// adapt the message to send "-fs receiver.pseudo"
								// and keep the sender's nickname
								strcpy(buffer2, token);
								strcat(buffer2, "\n");
								Sendline(y.fd, token, strlen(token));
								Readline(y.fd, buffer2, MAXBUF-1);
								// remove the sub-header
								strcpy(buffer2, &(buffer2[3]));
								printf("Client answer: %s", buffer2);
								buffer2[strlen(buffer2)-1] = '\0';
								strcat(buffer2, ":");
								strcat(buffer2, buffer6);
								strcpy(buffer6, "101");
								strcat(buffer6, buffer2);
								// send the answer to all servers in this form
								// "101-fs2 receiver:sender
								for (k=0; k< MAX_CLIENTS; k++){
									if (client[k].type == 1){
										Sendline(client[k].fd, buffer6, strlen(buffer6));
										printf("Forwarding to servers: %s", buffer6);
									}
								}
								for (k=0; k < serv_count; k++){
									Sendline(*server_sock[atoi(argv[2])-2+k], buffer6, strlen(buffer6));
									printf("Sending to servers: %s", buffer2);
								}
							}

						}

						/* If the sub-header of the buffer is "-fs2":
							a client is waiting for a connect() from the
							sender; forward the IP:PORT to the sender */
						if (buffer2[0] == '-' && buffer2[1] == 'f'
								&& buffer2[2] == 's' && buffer2[3] == '2'){
							strcpy(cp, &buffer2[5]);
							token3 = strtok(cp, delimiters);
							token4 = strtok(NULL, delimiters);
							token5 = strtok(NULL, delimiters);
							sprintf(buffer2,"-fs2 %s:%s\n", token3, token4);
							y.fd = -1;
							// find the sender
							for (k=0; k< MAX_CLIENTS; k++){
								if (client[k].pseudo!=NULL){
									if (!strncmp(client[k].pseudo, token5,
											minChar(strlen(token5),
													strlen(client[k].pseudo)))){
										y = client[k];
										break;
									}
								}
							}
							if (y.fd > 0) {
								//forward the message
								Sendline(y.fd, buffer2, strlen(buffer2));
							}

						}

						/* If the sub-header of the buffer is "->":
							a client sent a message in unicast mode */
						if (buffer2[0] == '-' && buffer2[1] == '>'){
							strcpy(buffer2, &buffer2[3]); //remove the 101 header,
							// then buffer2 contains the pseudo
							Readline(i, buffer5, MAXBUF-1); //read the message
							y.fd = -1;
							// find the client's socket
							for (k=0; k< MAX_CLIENTS; k++){
								if (client[k].pseudo!=NULL){
									if (!strncmp(client[k].pseudo, buffer2,
											minChar(strlen(buffer2),
													strlen(client[k].pseudo)))){
										y = client[k];
									}
								}
							}
							if (y.fd > 0){
								// send him the message
								Sendline(y.fd, buffer5, strlen(buffer5));
							}
						}
						else {
							/* Message are broadcasted to all clients by default
							 * if they are not specific commands
							 * if the sub-header is re, clients are notified that
							 * a client is connected */
							if (buffer2[0] == 'r' && buffer2[1] == 'e'){
								strcpy(buffer2, &buffer2[2]);
								buffer2[strlen(buffer2)-1] = '\0';
								strcat(buffer2, " vient de rejoindre le tchat\n");
							}
							if ( buffer2[0] != '-' && buffer2[1] != '>'
									&& buffer2[0] != 'l' && buffer2[1] != 's'
											&& buffer2[0] != 'f' && buffer2[1] != '-'
													&& buffer2[0] != '\n') {
								for (k=0; k< MAX_CLIENTS; k++){
									if (client[k].pseudo!=NULL && client[k].fd!=i
											&& client[k].type == 2){
										if(Sendline(client[k].fd, buffer2 , strlen(buffer2)) < 0) {
											perror("Sendline");
										}
									}
								}
							}


						}

					}
					memset(buffer4,'\0',sizeof(buffer4));

					/* If the header of the buffer is "001":
					 * the message is coming from a client */
					if (buffer[0] == '0' && buffer[1] == '0' && buffer[2] == '1' ){
						strcpy(buffer4, buffer);
						buffer4[0] = '1'; // change the header to 101
						if (buffer[3] == 'f' && buffer[4] == '-' && buffer[5] == '>' ){
							buffer4[strlen(buffer4)-1] = '\0';
							strcat(buffer4, ":");
							strcat(buffer4, client[i].pseudo); // add the sender's nickname
						}
						// broadcast the message to all servers
						for (k=0; k< max+1; k++){
							if (client[k].type == 1){
								Sendline(client[k].fd, buffer4, strlen(buffer4));
								printf("Sending to a server: %s", buffer4);
							}
						}
						for (k=0; k < serv_count; k++){
							Sendline(*server_sock[atoi(argv[2])-2+k], buffer4, strlen(buffer4));
							printf("Sending to a server: %s", buffer4);
						}
					}
					memset(buffer4,'\0',sizeof(buffer4));

					/* If the header of the buffer is "001":
					 * the message is coming from a client */
					if (buffer[0] == '0' && buffer[1] == '0' && buffer[2] == '1'){
						client[i].type = 2; // set the client's type to 2

						for (k=0; k< max+1; k++){
							if (client[k].type == 2){
								printf("A client is connected\n");
							}
						}

						strcpy(buffer, &(buffer[3])); // remove the 001 header

						/* Ask for a nickname on the first connexion */
						if (client[i].pseudo == NULL
								&& client[i].type == 2){
							Sendline(i, msg, strlen(msg));
							Readline(i, buffer1, MAXBUF-1);
							strcpy(buffer1, &(buffer1[3]));
							client[i].pseudo = malloc(MAXBUF*sizeof(char));
							strcpy(client[i].pseudo, buffer1);
							printf("Client's nickname set to: %s", client[i].pseudo);
							fflush(stdout);
							/* Notify other servers of a client's connexion */
							memset(buffer4,'\0',sizeof(buffer4));
							strcpy(buffer4, "101re");
							strcat(buffer4, client[i].pseudo);
							client_glob[client_count].pseudo = malloc(MAXBUF*sizeof(char));
							// Add client to global list
							strcpy(client_glob[client_count].pseudo, client[i].pseudo);
							client_count++;
							// Broadcat to servers the client's connexion notification
							for (k=0; k< max+1; k++){
								if (client[k].type == 1){
									Sendline(client[k].fd, buffer4, strlen(buffer4));
									printf("Forwarding to servers: %s", buffer4);
								}
							}
							for (k=0; k < serv_count; k++){
								Sendline(*server_sock[atoi(argv[2])-2+k], buffer4, strlen(buffer4));
								printf("Forwarding to servers: %s to %d", buffer4, *server_sock[atoi(argv[2])-2+k]);
							}
							printf("Here is the number of connected servers: %d\n", serv_count);

						}
						memset(buffer4,'\0',sizeof(buffer4));

						/* If the sub-header of the buffer is "ls":
						 * client asks for the list of connected clients */
						if ( buffer[0] == 'l' && buffer[1] == 's'){
							printf("Liste des connectés:\n");
							for (k=0; k< MAX_CLIENTS; k++){
								if (client_glob[k].pseudo!=NULL){
									printf("%s", client_glob[k].pseudo);
									strcat(list, client_glob[k].pseudo);
								}
							}
							// send the list
							Sendline(i, list, strlen(list));// The list is sent
						}
						/* If the sub-header of the buffer is "->":
						 * client sends a message in unicast */
						if ( buffer[0] == '-' && buffer[1] == '>'){
							printf("DEMANDE D'UNICAST\n");
							strcpy(buffer3, &(buffer[3])); // get the nickname of the recipient
							x.fd = i;
							// search for the socket of the recipient
							for (k=0; k< max+1; k++){
								if (client[k].pseudo!=NULL){
									if (!strncmp(client[k].pseudo, buffer3,
											minChar(strlen(buffer3),
													strlen(client[k].pseudo)))){
										x = client[k];
									}
								}
							}
							Sendline(i, msg3, strlen(msg3)); // ask for the message
							Readline(i, buffer3, MAXBUF-1 );
							strcpy(buffer3, &(buffer3[3]));
							if (x.fd != i ){ // if the recipient was found, then send him the message
								Sendline(x.fd, buffer3, strlen(buffer3));
							}
							else {
								// if the recipient wasn't found in the local list, the unicast
								// message is broadcasted to all servers
								printf("unable to find client, sending through server %s", buffer3);
								for (k=0; k< max+1; k++){
									if (client[k].type == 1){
										Sendline(client[k].fd, buffer3, strlen(buffer3));
										printf("Forwarding to servers: %s", buffer3);
									}
								}
								for (k=0; k < serv_count; k++){
									Sendline(*server_sock[atoi(argv[2])-2+k], buffer3, strlen(buffer3));
									printf("Forwarding to servers: %s", buffer3);
								}
							}
						}
						/* If the sub-header of the buffer is "g+>":
						 * client creats a group (group lists are available only locally) */
						if ( buffer[0] == 'g' && buffer[1] == '+' && buffer[2] == '>'){
							printf("DEMANDE DE CREATION DE GROUPE\n");
							groups[i].num_members = 0;
							groups[i].owner = client[i];
							groups[i].members[groups[i].num_members] = client[i];
							sprintf(buffer3, "Groupe créer au nom de %s", groups[i].owner.pseudo);
							Sendline(i, buffer3, strlen(buffer3));
						}
						/* If the sub-header of the buffer is "g->":
						 * client wants to join a group */
						if ( buffer[0] == 'g' && buffer[1] == '-' && buffer[2] == '>'){
							Sendline(i, msg2, strlen(msg2));
							Readline(i, buffer3, MAXBUF-1 );
							strcpy(buffer3, &(buffer3[3])); // get the nickname of the group's owner
							// from command line
							// find the group in the list
							for (k=0; k< MAX_GROUPS; k++){
								if (groups[k].owner.pseudo != NULL){
									if (!strncmp(groups[k].owner.pseudo, buffer3,
											minChar(strlen(buffer3),
													strlen(groups[k].owner.pseudo)))){
										found = k;
									}
								}
							}
							if (found >= 0){
								// if the group was found
								printf("AJOUT A UN GROUPE\n");
								groups[found].num_members++; // increment the number of members
								// add the sender to the group
								groups[found].members[groups[found].num_members] = client[i];

							}

						}

						found = -1;
						/* If the sub-header of the buffer is "gls":
						 * client asked for a group list
						 * (group lists are available only locally) */
						if ( buffer[0] == 'g' && buffer[1] == 'l' && buffer[2] == 's'){
							admin = malloc(strlen(&(buffer[3]))* sizeof(char));
							admin = &(buffer[3]); // get the owner's nickname
							// find him in the list of groups
							for (k=0; k< MAX_GROUPS; k++){
								if (groups[k].owner.pseudo != NULL){
									if (!strncmp(groups[k].owner.pseudo, admin,
											minChar(strlen(admin),
													strlen(groups[k].owner.pseudo)))){
										found = k;
									}
								}
							}
							if (found > 0){ // if the groupe(owner) was found
								printf("Groupe owner %s\n", groups[found].owner.pseudo);
								for (j=0; j< MAX_MEMBERS; j++){
									if (groups[found].members[j].pseudo != NULL){
										printf("%s\n", groups[found].members[j].pseudo);
										strcat(list2, groups[found].members[j].pseudo);
									}
								}
								// then send the list
								Sendline(i, list2, strlen(list2));// The list is sent
							}
						}

						k = 0;

						/* If the sub-header of the buffer is "f->":
						 * client wants to send a file */
						if (buffer[0] == 'f' && buffer[1] == '-' && buffer[2] == '>'){
							x.fd = i;
							strcpy(buffer, &(buffer[4])); // get the recipient's nickname
							printf("DEMANDE D'ENVOI DE FICHIER\n");
							for (k=0; k< max+1; k++){
								if (client[k].pseudo!=NULL){
									if (!strncmp(client[k].pseudo, buffer,
											minChar(strlen(buffer),
													strlen(client[k].pseudo)))){
										x = client[k];
										break;
									}
								}
							}
							printf("DE: %sPOUR: %s\n", client[i].pseudo, buffer);
							strcat(msg5, client[i].pseudo);
							if (x.fd != i){ // if the recipient was found, send him notification
								// so that he accept() the direct connexion on a new
								// socket
								Sendline(x.fd, msg5, strlen(msg5));
								Readline(x.fd, buffer3, MAXBUF-1);
								strcpy(buffer3, &(buffer3[3]));
								// read the answer containing IP:PORT and send it to the sender
								if (buffer3[0] == '-' && buffer3[1] == 'f' && buffer3[2] == 's'
										&& buffer3[3] == '2'){
									Sendline(client[i].fd, buffer3, strlen(buffer3));
								}
							}
						}

						/* If there is no subheader, the message is brodcasted to all clients
						 * by default*/
						if (buffer[0] != 'f' && buffer[0] != '0'
								&& buffer[0] != '1' && buffer[0] != '-'
										&& buffer[0] != 'g' && buffer[0] != '\n'){
							printf("Received: %s for sockfd :%d\n",buffer, i);
							fflush(stdout);
							for (k=0; k< max+1; k++){
								if (client[k].pseudo!=NULL && client[k].fd!=i
										&& client[k].type == 2){
									if(Sendline(client[k].fd, buffer , strlen(buffer)) < 0) {
										perror("Sendline");
									}
								}
							}
						}

					}
				}
			}
		}
	}
	/* Clean up */
	for (i=0; i<MAX_CLIENTS; i++){
		if (client[i].fd != 0){
			close(client[i].fd);
		}
	}
	for (k=0; k < serv_count; k++){
		close(*server_sock[atoi(argv[2])-2+k]);
	}
	return 0;
}
